package com.duowan.cms.common.repository;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;

import com.duowan.cms.common.domain.DomainObject;
import com.duowan.cms.common.domain.Page;
import com.duowan.cms.common.dto.DataTransferObject;
import com.duowan.cms.common.dto.PageSearchCriteria;
import com.duowan.cms.common.exception.BaseCheckedException;
import com.duowan.cms.common.repository.SQLParameterPair.SQLOperateType;
import com.duowan.cms.common.service.DoToDtoConvertorFactory;

/**
 * 基于Mybatis的领域对象仓库基类
 * 
 * @author coolcooldee yzq
 * @version 1.0
 * @created 04-九月-2012 10:55:04
 */

public abstract class AbstractDomainObjectRepository<O extends DomainObject, D extends DataTransferObject, C extends PageSearchCriteria> implements DomainObjectRepository<O, D, C> {

    public static Logger logger = Logger.getLogger(DomainObjectRepository.class);
    
    public final static String  REPOSITORY_ACCESS_ERROR = AbstractDomainObjectRepository.class.getName().concat("001");

    protected SqlSessionFactory sqlSessionFactory;

    private String line = System.getProperty("line.separator");

    private static Long TIME_OUT = 500L;

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public String getSql(SqlSession session, String methodName, Object parameter) {
            //return MyBatisSQLHelper.getSql(session, methodName, parameter); 
            return session.getConfiguration().getMappedStatement(methodName).getBoundSql(parameter).getSql();
    }

    /**
     * 获取领域对象的类
     * @return
     */
    protected abstract Class<O> getDomainObjectClass();

    protected String getPrefix() {
        return getDomainObjectClass().getName();
    }

    /**
     * 保存领域对象并返回其ID
     * @param object
     */
    public void save(O object) {
        updateOne(new SQLParameterPair(object.getClass().getName() + ".save", object, SQLOperateType.INSERT));
    }

    /**
     * 更新领域对象
     * @param object 更新领域对象
     */
    public void update(O object) {
        updateOne(new SQLParameterPair(object.getClass().getName() + ".update", object, SQLOperateType.UPDATE));
    }

    /**
     * 删除领域对象
     * @param object
     */
    public void delete(O object) {
        updateOne(new SQLParameterPair(object.getClass().getName() + ".delete", object, SQLOperateType.DELETE));
    }

    /**
     * 删除集合中的领域对象
     * @param objects
     */
    // public void deleteAll(List<O> objects) {
    //
    // }

    /**
     * 根据标识获取领域对象，当领域对象不存在时，不能返回null，需要抛出ObjectNotFoundException
     * @param id
     */
    // public O getById(Serializable id) {
    // return null;
    // }

    /**
     * 根据ID的集合获取匹配的集合对象
     * @param ids
     */
    // public List<O> getByIds(List<Serializable> ids) {
    // return null;
    // }

    /**
     * 根据ids删除持久化对象，返回删除的数量
     * @param ids
     */
    // public int deleteByIds(List<Serializable> ids) {
    // return 0;
    // }

    /**
     * 查询
     * @param searchCriteria 查询条件
     */
    // public List<D> search(C searchCriteria) {
    // return null;
    // }

    /**
     * 按条件分页查询
     * @param pageSize 每页数量
     * @param pageNo 查询页码
     * @param searchCriteria 查询条件
     */
    @SuppressWarnings("unchecked")
    public Page<D> pageSearch(C searchCriteria) {
        long start = System.currentTimeMillis();
        

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("search", searchCriteria);
        Integer offset = 0;
        if(null != searchCriteria.getPageSize()){
            offset = (searchCriteria.getPageNo() - 1) * searchCriteria.getPageSize();
            map.put("offset", offset);
        }

        SqlSession session = sqlSessionFactory.openSession();
        List<O> data = null;
        Integer totalCount = 0;
        try {
            data = session.selectList(getDomainObjectClass().getName() + ".pagedSearch", map);
            totalCount = session.selectOne(getDomainObjectClass().getName() + ".pagedSearch" + "-count", map);
            session.commit();
            
            long end = System.currentTimeMillis();
            if (end - start > TIME_OUT) {
                String pagedSearchSQL = getSql(session, getPrefix() + ".pagedSearch", map);
                String pagedSearchCountSQL = getSql(session, getPrefix() + ".pagedSearch-count", map);
                logger.warn("query sql cost time:" + (end - start) + "ms. " + line + "sql:" + pagedSearchSQL + line + "以及sql:" + pagedSearchCountSQL);
            }
        } catch (RuntimeException e) {
            logger.warn("Exception sql:" + getSql(session, getPrefix() + ".pagedSearch", map) 
                    + line + "or Exception sql:" + getSql(session, getPrefix() + ".pagedSearch-count", map), e);
            session.rollback();
            throw e;
        } finally {
            if (null != session)
                session.close();
        }

        List<D> Infos = DoToDtoConvertorFactory.getConvertor(this.getDomainObjectClass()).dos2Dtos(data);
        
        return new Page<D>(offset, searchCriteria.getPageSize(), totalCount, Infos);
    }
    
    @SuppressWarnings("unchecked")
    public List<D> listSearch(C searchCriteria) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("search", searchCriteria);
        if(null != searchCriteria.getPageSize()){
            Integer offset = (searchCriteria.getPageNo() - 1) * searchCriteria.getPageSize();
            map.put("offset", offset);
        }
        List<O> list = (List<O>)queryObject(new SQLParameterPair(getPrefix() + ".pagedSearch", map, SQLOperateType.SELECT_LIST));
        return DoToDtoConvertorFactory.getConvertor(this.getDomainObjectClass()).dos2Dtos(list);
    };

    /**
     * 根据sqlId和参数，查询返回domain对象
     * @param sqlId
     * @param parameter
     * @return
     */
    @SuppressWarnings("unchecked")
    protected O selectOne(String sqlId, Object parameter) {
        return (O) queryObject(new SQLParameterPair(sqlId, parameter, SQLOperateType.SELECT_ONE));
    }

    /**
     * 计数查询
     * @param sqlId
     * @param parameter
     * @return
     */
    protected Integer selectCount(String sqlId, Object parameter) {
        return (Integer) queryObject(new SQLParameterPair(sqlId, parameter, SQLOperateType.SELECT_ONE));
    }

    /**
     * 根据sqlId和参数，查询返回dto对象列表
     * @param sqlId
     * @param parameter
     * @return
     */
    @SuppressWarnings("unchecked")
    protected List<D> selectList(String sqlId, Object parameter) {

        List<O> datas = (List<O>) queryObject(new SQLParameterPair(sqlId, parameter, SQLOperateType.SELECT_LIST));

        List<D> list = DoToDtoConvertorFactory.getConvertor(this.getDomainObjectClass()).dos2Dtos(datas);

        return list;
    }

    /**
     * 
     * @param sqlId
     * @param parameter
     * @return
     */
    protected Object queryObject(SQLParameterPair spp) {
        long start = System.currentTimeMillis();
        SqlSession session = sqlSessionFactory.openSession();

        Object object = null;

        try {
            if (SQLOperateType.SELECT_ONE == spp.getOperate()) {
                object = session.selectOne(spp.getSqlId(), spp.getParameter());
            } else if (SQLOperateType.SELECT_LIST == spp.getOperate()) {
                object = session.selectList(spp.getSqlId(), spp.getParameter());
            }
            session.commit();
            
            long end = System.currentTimeMillis();
            if (end - start > TIME_OUT) {
                logger.warn("query sql cost time:" + (end - start) + "ms. "+line+" sql:" + getSql(session, spp.getSqlId(), spp.getParameter()));
            }
            
        } catch (RuntimeException e) {
            logger.warn("Exception sql:" + getSql(session, spp.getSqlId(), spp.getParameter()), e);
            session.rollback();
            throw e;
        } finally {
            if (null != session)
                session.close();
        }
        
        return object;
    }

    /**
     * 数据库操作：insert,delete,update-->update;select-->query
     * @param list
     * @throws BaseCheckedException 
     */
    protected void updateList(List<SQLParameterPair> list) {
        long start = System.currentTimeMillis();
        SqlSession session = sqlSessionFactory.openSession();

        String sql = null;
        Object parameter = null;
        try {
            for (SQLParameterPair spp : list) {
                sql = spp.getSqlId();
                parameter = spp.getParameter();
                if (SQLOperateType.INSERT == spp.getOperate()) {
                    session.insert(sql, parameter);
                    continue;
                } else if (SQLOperateType.UPDATE == spp.getOperate()) {
                    session.update(sql, parameter);
                    continue;
                }else if (SQLOperateType.DELETE == spp.getOperate()) {
                    session.delete(sql, parameter);
                    continue;
                } 
            }
            session.commit();
            
            long end = System.currentTimeMillis();
            if (end - start > TIME_OUT) {
                StringBuffer sb = new StringBuffer();
                sb.append(line);
                for (SQLParameterPair tmp : list) {
                    sb.append(getSql(session, tmp.getSqlId(), tmp.getParameter())).append(line);
                }
                logger.warn("sql collection cost time:" + (end - start) + "ms." + sb.toString());
            }
            
        } catch (RuntimeException e) {
            logger.error("Exception sql:" + getSql(session, sql, parameter), e);
            session.rollback();
            throw e;
        } finally {
            if (null != session)
                session.close();
        }
    }

    protected void updateOne(SQLParameterPair spp) {
        long start = System.currentTimeMillis();
        SqlSession session = sqlSessionFactory.openSession();

        try {
            if (SQLOperateType.INSERT == spp.getOperate()) {
                session.insert(spp.getSqlId(), spp.getParameter());
            } else if (SQLOperateType.DELETE == spp.getOperate()) {
                session.delete(spp.getSqlId(), spp.getParameter());
            } else if (SQLOperateType.UPDATE == spp.getOperate()) {
                session.update(spp.getSqlId(), spp.getParameter());
            }
            session.commit();
            
            long end = System.currentTimeMillis();
            if (end - start > TIME_OUT) {
                logger.warn("sql cost time:" + (end - start) + "ms." + line + "sql:" + getSql(session, spp.getSqlId(), spp.getParameter()));
            }
            
        } catch (RuntimeException e) {
            logger.warn("Exception sql:" + getSql(session, spp.getSqlId(), spp.getParameter()), e);
            session.rollback();
            throw e;
        } finally {
            if (null != session)
                session.close();
        }
        
    }
}